package com.wushijia.config;


import com.wushijia.model.User;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;

/**
 * description: authenticated():只需要认证过后即可，不校验权限，资源认证服务
 *
 * .antMatchers("/product/**").access("#oauth2.hasScope('select') and hasRole('ROLE_USER')"):表达式语法，完成复杂的权限控制 .antMatchers("/product/**").hasAuthority()：基于权限的控制
 * .antMatchers("/product/**").hasRole()：基于角色的控制
 *
 * @author yang
 * @date 2018/5/18 16:35
 */
@Configuration
public class OAuth2ServerConfig {

  private static final String DEMO_RESOURCE_ID = "order";

  @Configuration
  @EnableResourceServer
  protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
      resources.resourceId(DEMO_RESOURCE_ID).stateless(true);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
      // @formatter:off
      http
          // Since we want the protected resources to be accessible in the UI as well we need
          // session creation to be allowed (it's disabled by default in 2.0.6)
          .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
          .and()
          .requestMatchers().anyRequest()
          .and()
          .anonymous()
          .and()
          .authorizeRequests()
          .antMatchers("/product/**").access("#oauth2.hasScope('select') and hasRole('ROLE_USER')")
          .antMatchers("/order/**").authenticated();
      // @formatter:on
    }
  }


  /**
   * description: 提供/oauth/authorize,/oauth/token,/oauth/check_token,/oauth/confirm_access,/oauth/error; oauth2官方只有4种授权方式，不过spring
   * securityoauth2把refresh_token也归为authorizedGrantTypes的一种，有"client_credentials", "refresh_token", "authorization_code", "password", "implicit"
   *
   * @author yang 认证服务
   * @date 2018/5/22 17:11
   */
  @Configuration
  @EnableAuthorizationServer
  protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Autowired
    AuthenticationManager authenticationManager;

    @Autowired
    PasswordEncoder passwordEncoder;

    @Autowired
    private UserDetailsService customizeUserDetailsServiceImpl;

    @Autowired
    @Qualifier("druidDataSource")
    private DataSource dataSource;

    /**
     * description:URL中的client为上文中通过ClientDetailsServiceConfigurer类指定的clientId， authorization_code的授权方式不需要 client_secret, 因此secret可以填写任意值
     *
     * @author yang
     * @date 2019/1/23 19:55
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
      clients.withClientDetails(clientDetails());
      //或者可以从内存中或者数据库中加载配置两个客户端,一个用于password认证一个用于client认证
//      clients.inMemory() //使用in-memory存储
//          .withClient("client_1")
//          .resourceIds(DEMO_RESOURCE_ID)
//          .authorizedGrantTypes("client_credentials", "refresh_token")
//          .scopes("select")
//          .authorities("client")
//          .secret(passwordEncoder.encode("123456"))
//          .and().withClient("client_2")
//          .resourceIds(DEMO_RESOURCE_ID)
//          .authorizedGrantTypes("password", "refresh_token")
//          .scopes("select")
//          .authorities("client")
//          .secret(passwordEncoder.encode("123456"))
//          .accessTokenValiditySeconds(7200);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
      endpoints
          .authenticationManager(authenticationManager)
          .userDetailsService(customizeUserDetailsServiceImpl)
          //允许 GET、POST 请求获取 token，即访问端点：oauth/token,默认只支持post
          .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
          .tokenServices(tokenServices());
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
      oauthServer.tokenKeyAccess("isAuthenticated()");
      oauthServer.checkTokenAccess("permitAll()");
      //允许表单认证
      oauthServer.allowFormAuthenticationForClients();
      oauthServer.passwordEncoder(passwordEncoder);
    }

    /**
     * description:声明TokenStore实现(存储token的位置，一般可使用redis)
     *
     * @author yang
     * @date 2018/5/28 16:32
     */
    @Bean
    public TokenStore tokenStore() {
      //使用内存或redis中tokenStore,基于Jwt(JwtTokenStore)
      //TokenStore inMemoryTokenStore = new InMemoryTokenStore();
      //TokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
      TokenStore jdbcTokenStore = new JdbcTokenStore(dataSource);
      return jdbcTokenStore;
    }

    /**
     * description:声明ClientDetails实现
     *
     * @author yang
     * @date 2019/1/24 10:40
     */
    @Bean
    public ClientDetailsService clientDetails() {
      return new JdbcClientDetailsService(dataSource);
    }

    /**
     * description:自定义令牌声明，添加额外的属性username和account
     *
     * @author yang
     * @date 2018/5/29 11:19
     */
    @Bean
    public TokenEnhancer tokenEnhancer() {
      return (accessToken, authentication) -> {
        if (accessToken instanceof DefaultOAuth2AccessToken) {
          DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;
          Map<String, Object> additionalInformation = new LinkedHashMap<>();
          additionalInformation.put("username",
              ((User) authentication.getUserAuthentication().getPrincipal()).getUsername() != null ? ((User) authentication.getUserAuthentication()
                  .getPrincipal())
                  .getUsername() : " ");
          additionalInformation.put("account",
              ((User) authentication.getUserAuthentication().getPrincipal()).getUsername() != null ? ((User) authentication.getUserAuthentication()
                  .getPrincipal()).getUsername() : "");
          token.setAdditionalInformation(additionalInformation);
        }
        return accessToken;
      };
    }

    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
      DefaultTokenServices tokenServices = new DefaultTokenServices();
      //默认false
      tokenServices.setSupportRefreshToken(true);
      tokenServices.setTokenStore(tokenStore());
      //tokenServices.setClientDetailsService(clientDetails());
      tokenServices.setTokenEnhancer(tokenEnhancer());
      tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30));
      return tokenServices;
    }

//    @Bean
//    @Primary
//    public JdbcClientDetailsService jdbcClientDetailsService() {
//      JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
//      return jdbcClientDetailsService;
//    }
//
//    @Bean
//    @Primary
//    public DefaultOAuth2RequestFactory defaultOAuth2RequestFactory() {
//      DefaultOAuth2RequestFactory oauth2RequestFactory =
//          new DefaultOAuth2RequestFactory(jdbcClientDetailsService());
//      return oauth2RequestFactory;
//    }
  }
}
